<?php

class Template
{
    /**
     * 存储模板变量
     */
    private $tplVars = array();

    /**
     * 模板文件的路径 eg: /path/to/template.phtml
     */
    protected $fullPathTemplateFile;

    /**
     * 模板的MimeType eg: text/html
     */
    protected $contentType;

    /**
     * 保存模板的输出内容
     */
    public $content;

    public $dateFormat = 'Y-m-d';
    public $timeFormat = 'Y-m-d H:i';

    /**
     * 模板的输出时候的一些处理开关，比如是否压缩，是否静态化URL
     */
    private $echoPolicy = 15;

    const STATICIZE_URLS        = 1;
    const REBUILD_EXTERNAL_REFS = 2;
    const HTML_COMPRESSOR       = 4;

    /**
     * 两个模板常量
     */
    public static $THEME_DOMAIN     = NULL;

    /**
     * 模板使用的插件
     */
    private $plugIn = array();

    /**
     * 默认的 Content-type
     */
    const DEFAULT_CONTENT_TYPE = 'text/html; charset=utf-8';

    function __construct()
    {
        $args   = func_get_args();
        $count  = count($args);
        switch ($count)
        {
            case 0:
                $this->contentType = self::DEFAULT_CONTENT_TYPE;
                break;

            case 1:
                if ($args[0]{0} === '/')
                {
                    $this->fullPathTemplateFile = $args[0];
                }
                else
                {
                    $this->content = $args[0];
                }

                $this->contentType = self::DEFAULT_CONTENT_TYPE;
                break;

            case 2:
                $this->fullPathTemplateFile = $args[0];
                $this->contentType          = $args[1];
                break;

            default:
                throw new cls_exception('invalid_args');
        }

        if (defined('MAIN_DOMAIN'))
        {
            self::$THEME_DOMAIN  = THEME_DOMAIN;
        }
        else
        {
            self::$THEME_DOMAIN = '';
        }
    }

    public function assign()
    {
        $args = func_get_args();

        switch (count($args))
        {
            case 1:
                foreach ($args[0] AS $name => $val)
                {
                    $this->tplVars[$name] = $val;
                }
                break;

            case 2:
                $this->tplVars[$args[0]] = $args[1];
                break;

            default:
                throw new cls_exception('Invalid Template->assign()');
                break;
        }

        return $this;
    }

    public function registerPlugIn()
    {
        $args = func_get_args();
        if (count($args) > 0)
        {
            $arg = $args[0];
            unset($args[0]);
            $this->plugIn[$arg] = $args;
        }
    }

    public function unregisterPlugIn($func)
    {
        $args = func_get_args();
        if (count($args) > 0)
        {
            unset($this->plugIn[$args[0]]);
        }
    }

    public function getTemplateVars()
    {
        return $this->tplVars;
    }

    public function display()
    {
        if (empty($this->contentType))
        {
            throw new cls_exception('invalid_content_type');
        }

        APF_header('Content-Type: ' . $this->contentType, true);

        $this->buildOutput();
        $this->handleFinalOutput();

        echo $this->content;
    }

    public function fetchContent()
    {
        $this->buildOutput();
        $this->handleFinalOutput();

        return $this->content;
    }

    public function import($file)
    {
        include $file;
    }

    private function buildOutput()
    {
        extract($this->tplVars);

        ob_start();

        require $this->fullPathTemplateFile;

        $this->content = ob_get_clean();

        foreach ($this->plugIn AS $plugin => $args)
        {
            array_unshift($args, $this);
            call_user_func_array($plugin, $args);
        }
    }

    private function handleFinalOutput()
    {
        // 是否压缩页面
        if (($this->echoPolicy & self::HTML_COMPRESSOR) === self::HTML_COMPRESSOR)
        {
            $this->content = self::compressHTML($this->content);
        }
    }

    static public function compressHTML($content)
    {
        $r_tag = uniqid(__FILE__ . '::' . __FUNCTION__);

        if (preg_match_all('~(?:<pre.*?>.*?</pre>)|(?:<textarea.*?>.*?</textarea>)|(?:<!--COMPRESSRESERVED-->.*?<!--/COMPRESSRESERVED-->)~s', $content, $matches))
        {
            $r_count   = count($matches[0]);
            $r_replace = array();
            for ($i = 0; $i < $r_count; ++$i)
            {
                $r_replace[$i] = '__' . $i . '__' . $r_tag . '__';
            }
            $content = str_replace($matches[0], $r_replace, $content);
        }
        else
        {
            $r_count = 0;
        }

        /* 需要更多测试 */
        $content = preg_replace('/<!--\s*[^[\]\n]+\s*-->/', '', $content); //  去单行注释

        // TODO
        $content = preg_replace_callback('%(<script.*?</script>)%s', 'strip_js_callback', $content);

        // 目前只去掉行前空白
        $content = preg_replace("/[\n\r]+\s*/", '', $content);

        if ($r_count > 0)
        {
            $content = str_replace($r_replace, $matches[0], $content);
        }

        return $content;
    }

    /**
     * 设置输出策略。策略选项如下：
     * STATICIZE_URLS，REBUILD_EXTERNAL_REFS，HTML_COMPRESSOR
     *
     * @param int option
     * @param boolean $value
     */
    public function setEchoPolicy($option, $value)
    {
        if ($value === true)
        {
            $this->echoPolicy |= $option;
        }
        else
        {
            if (($this->echoPolicy & $option) === $option)
            {
                $this->echoPolicy ^= $option;
            }
        }
    }

    public function setAllEchoPolicyOff()
    {
        $this->echoPolicy = 0;
    }

    public function setTemplateFile($file)
    {
        $this->fullPathTemplateFile = $file;
    }

    public function setTemplateContent($content)
    {
        $this->content = $content;
    }

    // 修正CSS链接地址，其实可以不需要这里解析
    private function rebuildStyleExternalRefs()
    {
        if (preg_match_all('/<link.*?href="([^"]+\.css)".*?>/', $this->content, $match))
        {
            if (strrpos($this->content, '</head>') !== false)
            {
                $this->content = str_replace(array_unique($match[0]), '', $this->content);
                $match[1]      = str_replace(self::$THEME_DOMAIN, '', $match[1]);

                $css0 = $css1 = array();
                foreach ($match[1] AS $val)
                {
                    if ($val{0} === '/')
                    {
                        $css0[] = $val;
                    }
                    else
                    {
                        $css1[] = $val;
                    }
                }

                $out = '';
                if ($css0 !== array())
                {
                    $css_cache_key = 'css_lasttime_' . md5(serialize($css0));
                    $css_lasttime = xc_get('Template', $css_cache_key);
                    if ($css_lasttime === false)
                    {
                        $css_time_hash = '';
                        $css_lasttime = get_files_last_time($css0, $css_time_hash);

                        $css_lasttime = $css_time_hash;

                        if ($css_lasttime !== false)
                        {
                            xc_add('Template', $css_cache_key, $css_lasttime, 60);
                        }
                    }
                    if ($css_lasttime !== false)
                    {
                        $css_lasttime = '?' . $css_lasttime . '.css';
                    }
                    else
                    {
                        $css_lasttime = '';
                    }
                    $out .= '<link href="' . self::$THEME_DOMAIN . implode(',', $css0) . $css_lasttime . '" rel="stylesheet" type="text/css" />';
                }
                foreach ($css1 AS $val)
                {
                    $out .= '<link href="' . $val . '" rel="stylesheet" type="text/css" />';
                }

                $this->content = str_replace('<head>', "<head>" . $out, $this->content);
            }
        }
    }

    /*
    protected function templateGetStaticFileURL($project, $app, $themeName, $styleFileName)
    {
        require_library('framework.PageUtil');
        $fullPath = PageUtil::getPagePath($themeName, $app, $project);
        $fullPath = substr($fullPath, 0, strlen($fullPath) - strlen('templates/'));
        return self::$THEME_DOMAIN . substr($fullPath, strlen(ROOT_PATH)) . $styleFileName;
    }

    protected function templateOutputStaticFileURL($project, $app, $themeName, $styleFileName)
    {
        $url = self::templateGetStaticFileURL($project, $app, $themeName, $styleFileName);
        echo "<link href=\"" . $url . "\" rel=\"stylesheet\" type=\"text/css\" />";
    }
     *
     */

    protected function put($var, $encode = true)
    {
        if ($encode === true && is_numeric($var) === false)
        {
            echo htmlspecialchars($var, ENT_QUOTES);
        }
        else
        {
            echo $var;
        }
    }

    protected function putThemeDomain($append = '')
    {
        if ($append === '')
        {
            echo self::$THEME_DOMAIN;
        }
        else
        {
            echo self::$THEME_DOMAIN . '/' . $append;
        }
    }

    public function putDomain($appName, $appendString = '')
    {
        static $domains = array();

        if (isset($domains[$appName]) === false)
        {
            $domains[$appName] = get_domain_name_by_app($appName, true);
        }

        if ($appendString === '')
        {
            echo $domains[$appName];
        }
        else
        {
            echo $domains[$appName] . '/' . $appendString;
        }
    }

    protected function htmlOptions($arr, $selected = 0, $arg = NULL)
    {
        if (empty($arr))
        {
            return '';
        }

        $fmt = '<option value="%s"%s>%s</option>';
        if ($arg === NULL)
        {
            $key = array_keys($arr);
            $val = array_values($arr);
        }
        else
        {
            $key = $arr;
            $val = $arg;
        }

        $rev = '';
        foreach ($key AS $k => $opt)
        {
            $sel  = ($opt == $selected) ? " selected='true'" : '';
            $rev .= sprintf($fmt, $opt, $sel, htmlspecialchars($val[$k], ENT_QUOTES));
        }

        echo $rev;
    }

    /**
     * create html radio box
     *
     * @param string $name
     * @param array $arr
     * @param mixed $selected
     * @param string $extra
     * @param string $separation
     */
    protected function htmlRadio($name, $arr, $selected = null, $extra = '', $separation = "&nbsp;")
    {
        $this->htmlCheckboxAndRadio('radio', $name, $arr, $selected, $extra, $separation);
    }

    /**
     * create html checkbox
     *
     * @param string $name
     * @param array $arr
     * @param mixed $selected
     * @param string $extra
     * @param string $separation
     */
    protected function htmlCheckbox($name, $arr, $selected = null, $extra = '', $separation = "&nbsp;")
    {
        $this->htmlCheckboxAndRadio('checkbox', $name, $arr, $selected, $extra, $separation);
    }

    private function htmlCheckboxAndRadio($type, $name, $arr, $selected, $extra, $separation)
    {
        $rev = '';
        $fmt = '<input name="%s" type="%s" value="%s"%s' . $extra . '/> <span>%s</span>';
        foreach ($arr AS $key => $val)
        {
            if (!is_array($selected))
            {
                $checked = ($key == $selected) ? " checked='true'" : '';
            }
            else
            {
                $checked = ((in_array("$key", $selected) && $selected !== array(0)) || ($selected === array(0) && $key == '0')) ? " checked='true'" : '';
            }
            $rev .= sprintf($fmt, $name, $type, $key, $checked, $val) . $separation;
        }

        echo $rev;
    }

    public function dateTimeFormat($time, $format = 'Y-m-d H:i:s')
    {
        echo date($format, $time);
    }

    public function setDateTimeFormat($format)
    {
        if (isset($format['date'])) $this->dateFormat = $format['date'];
        if (isset($format['time'])) $this->timeFormat = $format['time'];
        return $this;
    }

    public function dateFormat($date)
    {
        $this->loadLocalization();
        echo Localization::dateFormat($date);
    }

    public function timeFormat($time)
    {
        $this->loadLocalization();
        echo Localization::timeFormat($time);
    }

    public function currencyFormat($c, $decimal = false)
    {
        $this->loadLocalization();
        echo Localization::currencyFormat($c, $decimal);
    }

    private function loadLocalization()
    {
        // require_module(PROJECT . '.' . 'Localization');
    }

}

?>